For a direct band gap semiconductor, such as ZnO, the product of its absorption coefficient \(\alpha\) and incident photon energy \(hv\) is proportional to the square root of the energy difference between its band gap \(E_\text{g}\) and the incident photon energy \(E\):
\[ \alpha hv \propto (E - E_\text{g})^{1/2} \]
which can be transformed based on the definition of absorbance, \(A\), to
\[ (Ahv)^2 \propto E - E_\text{g} \]
To determine the optical band gap of a crystalline semiconductor, the ordinate is given by \(\alpha^{1/r}\), in which the exponent denotes the nature of the transition:
A Tauc plot is usually performed manually, that is, the band edge region is identified visually, and then a linear fit to the linear part of the band edge is performed manually and the intersect with the abscissa is calculated to arrive at the optical band gap.
If you have a large number of spectra, say from a time series experiment, its unusual for the band edge to stay constant over time, e.g., due to changing particle size, and so calculating the band gap by hand for each spectrum quickly becomes tedious.
We have developed a semi-automatic algorithm, that only requires you to supply four x values: two values that define the “plateau” on the low-energy side of the band edge, and two values that define the “plateau” on the high-energy side of the band edge.
This algorithm takes the energy and absorbance of a single spectrum as input, and returns the calculated optical band gap, as well as the fitted Tauc line and its goodness-of-fit parameters (adjusted R-squared and number of points).
Note that this package expects you to supply the UV-Vis spectrum as absorbance (unitless) vs energy (in electronvolts).
This sketch is a visual attempt to describe the algorithm we have
developed. The minimum necessary input is highE.limits and
lowE.limits, which define the dark green and dark red
circles, respectively. Using these x-values, we find all spectral
datapoints inside their defined ranges and create two linear fits, that
define the “floor” (red line) and “ceiling” (green line) below and above
the band edge, respectively. The vertical extent of the band edge can
then be calculated (dashed orange line, ceiling2floor), and
finally a subset of vertical range, whose extent is defined by the input
parameter bg.limits (which defaults to
c(0.3, 0.8) of the floor-to-ceiling distance), is used to
select the spectral datapoints that are fitted linearly (solid orange
line). The intersect of this linear fit with the x-axis is the optical
band gap.
This package includes a dataset with 141 UV-Vis spectra from an experiment on growing colloidal ZnO nanoparticles. This data is a subset of the data behind our publication (see Ahmed & Edvinsson, JPCC 2020).
r <- 0.5
lowE.limits <- c(3.0, 3.4)
highE.limits <- c(3.9, 4.3)
bg.limits <- c(0.3, 0.8)
anim_path <- here("man/figures/animation.gif")this.data <- uvvistauc::UVVisDataZnO
# convert wavelength in nm to energy in eV
this.data %<>% mutate(energy = photoec::wavelength2energy(wavelength))this.spectrum <-
this.data %>%
filter(sampleid == "N04H-00000")
spectrum <-
uvvistauc::tauc(
energy = this.spectrum$energy,
absorbance = this.spectrum$intensity,
r = r,
lowE.limits = lowE.limits,
highE.limits = highE.limits,
bg.limits = bg.limits)The uvvistauc() takes one one spectrum at a time. The
easiest way to work around this intended limitation is to wrap the
function call inside a loop.
What follows is a simple demonstration of this approach, using the ZnO dataset included with this package.
We can quickly create a rudimentary plot demonstrating the fitted “ceiling” (green) and “floor” (red) across the whole dataset, as well as the Tauc line itself (orange).
ggplot(spectra, aes(group = sampleid)) +
coord_cartesian(ylim = c(0, 7), xlim = c(3, 4.4)) +
geom_line(aes(x, floor), colour = "red", size = 0.2, alpha = 0.4) +
geom_line(aes(x, ceiling), colour = "#008000", size = 0.2, alpha = 0.4) +
geom_line(aes(x, y), colour = "black", size = 0.3) +
geom_line(aes(x, fit.tauc), colour = "orange", size = 0.3, alpha = 0.6) +
labs(x = "E/eV", y = paste0("(Abs)<sup>", 1/r, "</sup>")) +
theme(axis.title.y = element_markdown())We can also get a quick measure of the goodness of Tauc fit across all spectra by inspecting the statistics of the adjusted R-squared and the number of fitted datapoints:
summary(spectra$fit.adj_rsq)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.9381 0.9867 0.9921 0.9884 0.9948 0.9977
summary(spectra$fit.points)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 12.0 17.0 19.0 20.3 23.0 30.0The summary statistics can also be plotted, along with the calculated band gap values.
p1 <- ggplot(spectra) +
geom_point(aes(time, fit.Eg)) +
theme(axis.text = element_text(size = 11), axis.title.x = element_blank())
p2 <- ggplot(spectra) +
geom_point(aes(time, fit.adj_rsq)) +
theme(axis.text = element_text(size = 11), axis.title.x = element_blank())
p3 <- ggplot(spectra) +
geom_point(aes(time, fit.points)) +
labs(x = "Time/min") +
theme(axis.text = element_text(size = 11))
plot_grid(p1, p2, p3, nrow = 3, align = "v")From all this we can tell that for this dataset, the Tauc fits have a high quality over the entire dataset: R-squared values are very close to unity, and the number of fitted points never dip below 12, mostly staying around 20.
In this animation, we step through each spectrum (black) in our
dataset, showing its “ceiling” (green, based on the user-supplied
highE.limits) and its “floor” (red, based on the
user-supplied lowE.limits), as well as the actual data
points that the algorithm selected for fitting (yellow circles), the
resultant linear fit (orange), and the intersect of the Tauc fit with
the x-axis (large black circle).
I think this animation is really neat, but then I’m partial, of course ;-)